package cool.doudou.doudada.pay.core.api;

import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import cool.doudou.doudada.cipher.algorithm.enums.Algorithm;
import cool.doudou.doudada.cipher.algorithm.util.AesUtil;
import cool.doudou.doudada.pay.core.entity.PlaceOrderParam;
import cool.doudou.doudada.pay.core.entity.RefundParam;
import cool.doudou.doudada.pay.core.memory.WxPayMem;
import cool.doudou.doudada.pay.core.signer.WxSigner;
import cool.doudou.doudada.pay.core.util.HttpUtil;
import cool.doudou.doudada.pay.properties.WxPayProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.ObjectUtils;

import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

/**
 * WxPayApi
 *
 * @author jiangcs
 * @since 2022/06/23
 */
public class WxPayApi {
    private WxPayProperties wxPayProperties;

    /**
     * 加载平台证书
     */
    public void loadPlatformCertificate() {
        String reqAbsoluteUrl = "/v3/certificates";

        String result = HttpUtil.doGet4Wx(wxPayProperties.getServerAddress(), reqAbsoluteUrl, null, wxPayProperties.getMchId(), wxPayProperties.getPrivateKeySerialNumber());
        if (!ObjectUtils.isEmpty(result)) {
            JSONObject resultObj = JSONObject.parseObject(result, JSONObject.class);
            JSONArray dataArr = resultObj.getJSONArray("data");
            for (int i = 0, len = dataArr.size(); i < len; i++) {
                JSONObject dataObj = dataArr.getJSONObject(i);
                // 证书序列号
                String serialNo = dataObj.getString("serial_no");
                // 加密内容
                JSONObject encryptCertificateObj = dataObj.getJSONObject("encrypt_certificate");
                String nonce = encryptCertificateObj.getString("nonce");
                String ciphertext = encryptCertificateObj.getString("ciphertext");
                // 证书内容解密
                String associatedData = encryptCertificateObj.getString("associated_data");

                try {
                    String decryptStr = AesUtil.decryptGCM(wxPayProperties.getApiKeyV3(), nonce, ciphertext, associatedData);

                    // 证书内容转成证书对象
                    CertificateFactory cf = CertificateFactory.getInstance("X509");
                    X509Certificate x509Certificate = (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(decryptStr.getBytes(StandardCharsets.UTF_8)));
                    WxPayMem.certificateMap.put(serialNo, x509Certificate);
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }

    /**
     * 下单
     *
     * @param placeOrderParam 下单参数
     * @return 前端调用支付参数
     */
    public Map<String, String> place(PlaceOrderParam placeOrderParam) {
        if (ObjectUtils.isEmpty(placeOrderParam.getOutTradeNo())) {
            throw new RuntimeException("商户订单号不能为空");
        }
        if (placeOrderParam.getOutTradeNo().length() < 6) {
            throw new RuntimeException("商户订单号长度不能少于6位");
        }
        if (placeOrderParam.getOutTradeNo().length() > 32) {
            throw new RuntimeException("商户订单号长度不能多于32位");
        }

        if (!ObjectUtils.isEmpty(placeOrderParam.getTimeExpire()) && (!placeOrderParam.getTimeExpire().contains("T") || !placeOrderParam.getTimeExpire().contains("+"))) {
            throw new RuntimeException("交易结束时间格式错误");
        }

        JSONObject jsonObject = new JSONObject();
        jsonObject.put("appid", wxPayProperties.getAppId());
        jsonObject.put("mchid", wxPayProperties.getMchId());
        jsonObject.put("out_trade_no", placeOrderParam.getOutTradeNo());
        jsonObject.put("description", placeOrderParam.getDescription());
        if (!ObjectUtils.isEmpty(placeOrderParam.getTimeExpire())) {
            jsonObject.put("time_expire", placeOrderParam.getTimeExpire());
        }
        if (!ObjectUtils.isEmpty(placeOrderParam.getAttach())) {
            jsonObject.put("attach", placeOrderParam.getAttach());
        }
        jsonObject.put("notify_url", wxPayProperties.getNotifyUrl());
        JSONObject jsonAmount = new JSONObject();
        jsonAmount.put("total", placeOrderParam.getMoney().intValue());
        jsonAmount.put("currency", "CNY");
        jsonObject.put("amount", jsonAmount);
        JSONObject jsonPayer = new JSONObject();
        jsonPayer.put("openid", placeOrderParam.getUid());
        jsonObject.put("payer", jsonPayer);

        String reqAbsoluteUrl = "/v3/pay/transactions/jsapi";

        String result = HttpUtil.doPost4Wx(wxPayProperties.getServerAddress(), reqAbsoluteUrl, jsonObject.toJSONString(), wxPayProperties.getMchId(), wxPayProperties.getPrivateKeySerialNumber());
        if (ObjectUtils.isEmpty(result)) {
            throw new RuntimeException("下单通信失败");
        }
        JSONObject resultObj = JSONObject.parseObject(result, JSONObject.class);

        // 签名
        String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
        String nonceStr = UUID.randomUUID().toString();
        String packageStr = "prepay_id=" + resultObj.getString("prepay_id");

        Map<String, String> map = new HashMap<>(5);
        map.put("timestamp", timestamp);
        map.put("nonceStr", nonceStr);
        map.put("package", packageStr);
        map.put("signType", Algorithm.RSA.code());
        map.put("paySign", WxSigner.getPayment(wxPayProperties.getAppId(), timestamp, nonceStr, packageStr));
        return map;
    }

    /**
     * 查询
     *
     * @param outTradeNo 商户订单号
     * @return 订单信息
     */
    public String query(String outTradeNo) {
        if (ObjectUtils.isEmpty(outTradeNo)) {
            throw new RuntimeException("商户订单号不能为空");
        }

        Map<String, Object> params = new HashMap<>(1);
        params.put("mchid", wxPayProperties.getMchId());

        String reqAbsoluteUrl = "/v3/pay/transactions/out-trade-no/" + outTradeNo;

        return HttpUtil.doGet4Wx(wxPayProperties.getServerAddress(), reqAbsoluteUrl, params, wxPayProperties.getMchId(), wxPayProperties.getPrivateKeySerialNumber());
    }

    /**
     * 关闭
     *
     * @param outTradeNo 商户订单号
     * @return 关闭结果
     */
    public String close(String outTradeNo) {
        if (ObjectUtils.isEmpty(outTradeNo)) {
            throw new RuntimeException("商户订单号不能为空");
        }

        JSONObject jsonObject = new JSONObject();
        jsonObject.put("mchid", wxPayProperties.getMchId());

        String reqAbsoluteUrl = "/v3/pay/transactions/out-trade-no/" + outTradeNo + "/close";

        boolean flag = HttpUtil.doPostNoContent4Wx(wxPayProperties.getServerAddress(), reqAbsoluteUrl, jsonObject.toJSONString(), wxPayProperties.getMchId(), wxPayProperties.getPrivateKeySerialNumber());
        if (flag) {
            return JSONObject.of("code", "ok").toString();
        }
        return JSONObject.of("code", "fail").toString();
    }

    /**
     * 退款
     *
     * @param refundParam 退款参数
     * @return 退款信息
     */
    public String refund(RefundParam refundParam) {
        if (ObjectUtils.isEmpty(refundParam.getOutTradeNo())) {
            throw new RuntimeException("商户订单号不能为空");
        }

        JSONObject jsonObject = new JSONObject();
        jsonObject.put("out_trade_no", refundParam.getOutTradeNo());
        jsonObject.put("out_refund_no", refundParam.getOutRefundNo());
        jsonObject.put("reason", refundParam.getReason());
        JSONObject jsonAmount = new JSONObject();
        jsonAmount.put("total", refundParam.getMoney());
        jsonAmount.put("currency", "CNY");
        jsonAmount.put("refund", refundParam.getRefundMoney());
        jsonObject.put("amount", jsonAmount);

        String reqAbsoluteUrl = "/v3/refund/domestic/refunds";

        return HttpUtil.doPost4Wx(wxPayProperties.getServerAddress(), reqAbsoluteUrl, jsonObject.toJSONString(), wxPayProperties.getMchId(), wxPayProperties.getPrivateKeySerialNumber());
    }

    /**
     * 交易账单
     *
     * @param billDate 账单日期（格式yyyy-MM-dd）
     * @return 账单下载地址
     */
    public String tradeBill(String billDate) {
        if (ObjectUtils.isEmpty(billDate)) {
            throw new RuntimeException("账单日期不能为空");
        }

        Map<String, Object> params = new HashMap<>(1);
        params.put("bill_date", billDate);

        String reqAbsoluteUrl = "/v3/bill/tradebill";

        return HttpUtil.doGet4Wx(wxPayProperties.getServerAddress(), reqAbsoluteUrl, params, wxPayProperties.getMchId(), wxPayProperties.getPrivateKeySerialNumber());
    }

    /**
     * 下载账单
     *
     * @param billUrl 账单地址
     * @return 账单信息字节数组流
     */
    public ByteArrayInputStream downloadBill(String billUrl) {
        String[] billUrlArr = billUrl.split("\\?");
        if (billUrlArr.length != 2) {
            throw new RuntimeException("账单地址格式错误");
        }

        String reqAbsoluteUrl = billUrlArr[0].replace(wxPayProperties.getServerAddress(), "");

        String[] paramArr = billUrlArr[1].split("=");
        if (paramArr.length != 2) {
            throw new RuntimeException("账单地址参数格式错误");
        }
        Map<String, Object> params = new HashMap<>(1);
        params.put(paramArr[0], paramArr[1]);

        return HttpUtil.doGetInputStream4Wx(wxPayProperties.getServerAddress(), reqAbsoluteUrl, params, wxPayProperties.getMchId(), wxPayProperties.getPrivateKeySerialNumber());
    }

    @Autowired
    public void setWxPayProperties(WxPayProperties wxPayProperties) {
        this.wxPayProperties = wxPayProperties;
    }
}
